package com.service;

import com.alibaba.fastjson.JSON;
import com.aop.MultiTransaction;
import com.controller.WebSocket;
import com.google.common.collect.Maps;
import com.model.ExplainDetail;
import com.model.FrontUser;
import com.model.WordInfo;
import com.model.WordTable;
import com.model.form.BaseTableForm;
import com.model.pojo.UserLinkWord;
import com.model.response.Response;
import com.model.vo.*;
import com.pepository.dao.ExplainDetailRepository;
import com.pepository.dao.WordInfoRepository;
import com.pepository.dao.WordTableRepository;
import com.pepository.neo4j.WordRelationRepository;
import com.utils.ModelMapUtils;
import org.apache.logging.log4j.util.Strings;
import org.neo4j.ogm.model.Result;
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
import org.neo4j.springframework.data.core.Neo4jClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.neo4j.transaction.SessionFactoryUtils;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.websocket.EncodeException;
import java.io.IOException;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.*;


@Service
@EnableScheduling
public class WordInfoService {

    @Autowired
    private WordInfoRepository wordInfoRepository;
    @Autowired
    private WordTableRepository wordTableRepository;
    @Autowired
    private ExplainDetailRepository explainDetailRepository;
    @Autowired
    private WordRelationRepository wordRelationRepository;
    @Autowired
    private Neo4jClient neo4jClient;
    @Autowired
    private SessionFactory sessionFactory;

    private static Long OUT_TIME = 100000L;
    private static CopyOnWriteArrayList<UserLinkWord> waitLinkUsers = new CopyOnWriteArrayList<>();
    private static ConcurrentMap<String, WordInfo> wordCatch = new ConcurrentHashMap<>();

    @Scheduled(fixedDelay = 5000)
    public void schedule() {
        while(waitLinkUsers.size() > 0) {
            waitLinkUsers.forEach(link -> {
                // 检查是否超时(有可能是爬取的时候报错导致无法入库)
                if ((System.currentTimeMillis() - link.getJoinTime()) <= OUT_TIME) {
                    // 数据库没有代表还没入库
                    WordInfo wordInfo = wordCatch.get(link.getWord());
                    if (wordInfo != null) {
                        link.setWordId(wordInfo.getId());
                        addNeo4jWordTable(link);
                        WordTable wordTable = addDbWordTable(link);
                        try {
                            // websocket 到前端修改页面内容
                            this.flashViewSocket(link.getUserId(), Word.create(wordInfo, wordTable));
                        } catch (IOException e) {
                            e.printStackTrace();
                        } catch (EncodeException e) {
                            e.printStackTrace();
                        }

                        waitLinkUsers.remove(link);
                        wordCatch.remove(link.getWord());
                        System.out.println("userId:"+ link.getUserId() +"-[]-word: "+ link.getWord());
                    }
                }else {
                    waitLinkUsers.remove(link);
                    wordCatch.remove(link.getWord());
                    try {
                        WebSocket.sendMessage(link.getUserId(), Response.fail().setMsg("单词" + link.getWord() + "无数据资料, 无法录入!"));
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (EncodeException e) {
                        e.printStackTrace();
                    }
                    System.out.println("TIME OUT remove word:" + link.getWord());
                }

            });
        }
    }


    public void addLinkUser(Long userId, String word) {
        waitLinkUsers.add(new UserLinkWord().setUserId(userId).setWord(word).setJoinTime(System.currentTimeMillis()));
    }

    public boolean hasWord(String word) {
        return wordInfoRepository.findCountByWord(word) > 0;
    }

    /**
     * 入库单词
     * @param wordInfo
     */
    @MultiTransaction
    public void saveWord(WordInfo wordInfo) {
        // 单词写到数据库
        wordInfo = wordInfoRepository.save(wordInfo);
        WordInfo finalWordInfo = wordInfo;
        wordInfo.getDetails().forEach(detail -> {
            detail.setWordInfoId(finalWordInfo.getId());
        });
        // 解析写到数据库
        List<ExplainDetail> detailList = explainDetailRepository.saveAll(wordInfo.getDetails());
        // 同义词, 类型词关系建立
        detailList.forEach(detail -> {
            if(detail.getSynonyms() != null && !detail.getSynonyms().isEmpty()) {
                detail.getSynonyms().forEach(synonym ->
                        wordRelationRepository.mergeSynonymsWord(finalWordInfo, detail.getId(), synonym));
            }
            if(detail.getTypes() != null && !detail.getTypes().isEmpty()) {
                detail.getTypes().forEach(type ->
                        wordRelationRepository.mergeTypeWord(finalWordInfo, detail.getId(), type));
            }
        });
        // 同源词关系建立
        if(wordInfo.getFamilyWord() != null && !wordInfo.getFamilyWord().isEmpty()) {
            wordInfo.getFamilyWord().forEach(family -> {
                wordRelationRepository.mergeFamilyWord(finalWordInfo, family);
            });
        }
        wordCatch.put(wordInfo.getWord(), wordInfo);
    }

    // 根据word查询
    public WordInfo findByWord(String word) {
        return wordInfoRepository.findFirstByWord(word);
    }


    public Boolean userHasWord(Long userId, String word) {
        Neo4jClient.RecordFetchSpec<Map<String, Object>> result = neo4jClient.query(
                "match (w:USER_ID_"+ userId +"{word:\""+word+"\"}) " +
                "return w is not null as result").fetch();
        if(result.one().isPresent()) {
            Map map = result.one().get();
            return (Boolean) map.get("result");
        }else {
            return false;
        }
    }

    public void addWordTable(UserLinkWord link) {
        addNeo4jWordTable(link);
        addDbWordTable(link);
    }

    // 根据用户添加自己的Word标签
    public void addNeo4jWordTable(UserLinkWord link) {
        String cql = "MATCH (w:Word{dbId:"+link.getWordId()+"}) " +
                "SET w:USER_ID_" + link.getUserId();
        Session session = sessionFactory.openSession();
        session.query(cql, Maps.newHashMap());
    }

    // 删除用户添加自己的Word标签
    public Page queryUserPage(FrontUser user, BaseTableForm form) {
        return wordInfoRepository.findUserPage(user.getId(), PageRequest.of(form.getCurrentPage() - 1, form.getPageSize()));
    }

    public WordTable addDbWordTable(UserLinkWord link) {
        if(wordTableRepository.hasByUserIDAndWord(link.getUserId(), link.getWord()) >= 1) {
            return null;
        }else {
            WordTable table = new WordTable()
                    .setUserId(link.getUserId())
                    .setWord(link.getWord())
                    .setWordInfoId(link.getWordId())
                    .setDate(LocalDate.now());
            return wordTableRepository.save(table);
        }
    }

    @MultiTransaction
    public Datas getDatas(Long userID){
        String table = "USER_ID_" + userID;
        String cql1 = "match (f:"+table+")<-[s]-(t:"+table+") return f.dbId as from, t.dbId as to, s.dbId as dbId, type(s) as type";
        Session session = SessionFactoryUtils.getSession(sessionFactory);
        Result edgesResult = session.query(cql1, Maps.newHashMap());
        List<Edge> edgeList = ModelMapUtils.mapIterator(edgesResult.iterator(), Edge.class);

        String cql2 = "MATCH (n:"+table+") RETURN n.dbId as id, n.word as label";
        Result nodesResult = session.query(cql2, Maps.newHashMap());
        List<Node> nodeList = ModelMapUtils.mapIterator(nodesResult.iterator(), Node.class);
        return new Datas(nodeList, edgeList);
    }

    public List<Edge> getEdges(Long userId, Long wordId) {
        String table = "USER_ID_" + userId;
        String cql = "match (f:"+table+"{dbId:"+wordId+"})<-[s]-(t:"+table+") return f.dbId as from, t.dbId as to, s.dbId as dbId";
        Session session = sessionFactory.openSession();
        Result edgesResult = session.query(cql, Maps.newHashMap());
        List<Edge> edgeList = ModelMapUtils.mapIterator(edgesResult.iterator(), Edge.class);
        return edgeList;
    }


    public void flashViewSocket(Long userId, Word word) throws IOException, EncodeException {
        // 单词本cb
        WebSocket.sendMessage(userId, Response.success(word).setMsg("/addWord"));
        // 关系图片cb
        Node node = new Node().setId(word.getWordId()).setLabel(word.getWord());
        List<Node> nodes = new ArrayList<>(1);
        nodes.add(node);
        List<Edge> edges = this.getEdges(userId, word.getWordId());
        WebSocket.sendMessage(userId, Response.success(new Datas(nodes, edges)).setMsg("/addData"));
    }

    @MultiTransaction
    public WordCard getWordCardByWordInfoId(Long userId, Long wordId) {
        WordInfo wordInfo = wordInfoRepository.findById(wordId).get();
        // 数据库部分录入
        WordCard wordCard = new WordCard();
        wordCard.setWord(wordInfo.getWord());
        wordCard.setExplained(wordInfo.getExplained());
        wordCard.setUnderstand(wordInfo.getUnderstand());
        List<ExplainDetail> details = explainDetailRepository.findAllByWordInfoIdOrderById(wordId);
        details.forEach(ex -> {
            Explain explain = new Explain();
            explain.setExplained(ex.getExplained());
            explain.setType(ex.getType());
            if(!Strings.isEmpty(ex.getExamplesJson())) {
                explain.setExamples(JSON.parseObject(ex.getExamplesJson(), List.class));
            }
            wordCard.getExplainMap().put(ex.getId(), explain);
        });
        return wordCard;
    }
}
